package com.sarc.games;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.util.ArrayList;
import java.util.concurrent.locks.ReentrantLock;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

import org.opencv.core.Mat;

import android.graphics.Bitmap;
import android.opengl.GLSurfaceView.Renderer;
import android.opengl.GLUtils;
import android.util.Log;

public class TouchBalls /*extends Thread*/ implements Renderer {
	private static final String TAG = "SARC.TOUCHBALLS";
	SensorMathematics sensor;
	Ball[][] balls = new Ball[4][4];
	ReentrantLock camPosLock, camImageLock;
	
	Mat rv=null, 
		tv=null, 
		image = null;
	
	public TouchBalls(SensorMathematics sensor)
	{
		this.sensor = sensor;
        for (int i = 0; i < 4; i++)
        {
        	for (int j = 0; j < 4; j++){
        		float[] newPt = new float[]{-3 + 2*i, -3 + 2*j, -8};
        		balls[i][j] = new Ball(newPt);
        	}
        }
         camPosLock = new ReentrantLock();
         camImageLock = new ReentrantLock();
	}
	
	public void setNewCameraPosition(Mat rv, Mat tv)
	{
		camPosLock.lock();
		try
		{
			if (this.rv == null)
			{
				rv = new Mat(rv.size(), rv.type());
			}
			if (this.tv == null)
			{
				tv = new Mat(tv.size(), tv.type());
			}
			rv.copyTo(this.rv);
			tv.copyTo(this.tv);
		}
		finally
		{
			camPosLock.unlock();
		}
	}
	
	public void setNewImageBuffer(Mat img)
	{
		camImageLock.lock();
		try
		{
			if (this.image == null)
			{
				this.image = new Mat(img.size(), img.type());
			}
			img.copyTo(image);
		}
		finally
		{
			camImageLock.unlock();
		}
		//this.image = img;
	}

	@Override
	public void onDrawFrame(GL10 gl) {
		// TODO Auto-generated method stub
		GLinitial(gl);
        GLend(gl);
        
        float[] p1 = new float[]{5, 0, -8};
        float[] p2 = new float[]{0, 0, -8};
        float[] p3 = new float[]{0, 5, -8};
        
        touchBalls(sensor.returnIntersection(sensor.vec, p1, p2, p3));
        
        for (int i = 0; i < 4; i++){
        	for (int j = 0; j < 4; j++){
        		drawSphere(gl, balls[i][j].returnPos());
        	}
        }
        
        
        //difference = sensor.returnIntersection(sensor.vec, p1, p2, p3);
        drawSphere(gl, sensor.returnIntersection(sensor.vec, p1, p2, p3));
        
        drawSphere (gl, new float[]{0, 0, -10}); 
        
        drawPlane(gl, new float[]{0, 0, -8}, new float[]{1, 0, 0}, 0f);
        //Log.d(TAG, "drawFrame");
	}

	@Override
	public void onSurfaceChanged(GL10 gl, int width, int height) {
		// TODO Auto-generated method stub
		gl.glViewport(0, 0, width, height);
        // set projection matrix
        float ratio = (float) width / height;
        gl.glMatrixMode(GL10.GL_PROJECTION);
        gl.glLoadIdentity();
        gl.glFrustumf(-ratio, ratio, -1, 1, 1, 30);
        //Log.d(TAG, "surfaceChanged");
	}

	@Override
	public void onSurfaceCreated(GL10 gl, EGLConfig arg1) {
		// TODO Auto-generated method stub
		gl.glDisable(GL10.GL_DITHER);
        // clear screen in white
        gl.glClearColor(0,0,0,1);
		//Log.d(TAG, "surface created");
	}
	
	public void GLinitial (GL10 gl){
		gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
        // set-up modelview matrix
		gl.glEnable(GL10.GL_DEPTH_TEST);
        gl.glMatrixMode(GL10.GL_MODELVIEW);
        gl.glLoadIdentity();
        gl.glRotatef(90, 0, 0, 1);
        gl.glMultMatrixf(sensor.rotationMatrix, 0);
        // draw our object
        gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
        gl.glEnableClientState(GL10.GL_COLOR_ARRAY);
	}
	
	public void GLend (GL10 gl){
		
		ByteBuffer mBuff = ByteBuffer.allocateDirect(sensor.initialRotation.length*4);
        mBuff.order(ByteOrder.nativeOrder());
        FloatBuffer multBuff = mBuff.asFloatBuffer();
        multBuff.put(sensor.initialRotation);
        multBuff.position(0);
        gl.glMultMatrixf(multBuff);
		
	}
	
	public void drawSphere(GL10 gl, float[] translation){
		
		int hori = 8;
		int vert = 4;
		float[] vertex = new float[hori * vert * 6 + 3]; //= {-1, 0, 0,	};
		
		float[] color = new float[hori * vert * 8 + 4]; //= {1, 1, 1, 1};
		
		ArrayList<Float> vertexList = new ArrayList<Float>();
		vertexList.add((float) -1);
		vertexList.add((float) 0);
		vertexList.add((float) 0);
		ArrayList<Float> colorList = new ArrayList<Float>();
		colorList.add((float) 1);
		colorList.add((float) 1);
		colorList.add((float) 1);
		colorList.add((float) 1);
		
		for (float i = 0; i < hori; i++){
			for (float j = 1; j <= vert; j++){
				float l = (float) Math.pow(-1, i + 1);
				float r = (float) Math.sin(j * Math.PI/vert);
				vertexList.add((float) (l*Math.cos(j*Math.PI/vert)));
				vertexList.add((float) (r*Math.cos(i*2*Math.PI/hori)));
				vertexList.add((float) (r*Math.sin(i*2*Math.PI/hori)));
				vertexList.add((float) (l*Math.cos(j*Math.PI/vert)));
				vertexList.add((float) (r*Math.cos((i+1)*2*Math.PI/hori)));
				vertexList.add((float) (r*Math.sin((i+1)*2*Math.PI/hori)));
				
				//float[] newPt = new float[]{(float) (l*Math.cos(j*Math.PI/vert)), (float) (r*Math.cos(i*2*Math.PI/hori)), (float) (r*Math.sin(i*2*Math.PI/hori)),	(float) (l*Math.cos(j*Math.PI/vert)), (float) (r*Math.cos((i+1)*2*Math.PI/hori)), (float) (r*Math.sin((i+1)*2*Math.PI/hori))};
				//float[] tmp = new float[vertex.length + newPt.length];
				//System.arraycopy(vertex, 0, tmp, 0, vertex.length);
				//System.arraycopy(newPt, 0, tmp, vertex.length, newPt.length);
				
				colorList.add((2 - (float) (l*Math.cos(j*Math.PI/vert))) / 3);
				colorList.add((2 - (float) (l*Math.cos(j*Math.PI/vert))) / 3);
				colorList.add((2 - (float) (l*Math.cos(j*Math.PI/vert))) / 3);
				colorList.add(1f);
				colorList.add((2 - (float) (l*Math.cos(j*Math.PI/vert))) / 3);
				colorList.add((2 - (float) (l*Math.cos(j*Math.PI/vert))) / 3);
				colorList.add((2 - (float) (l*Math.cos(j*Math.PI/vert))) / 3);
				colorList.add(1f);
				//float[] newColor = {(2 - (float) (l*Math.cos(j*Math.PI/vert))) / 3, (2 - (float) (l*Math.cos(j*Math.PI/vert))) / 3, (2 - (float) (l*Math.cos(j*Math.PI/vert))) / 3, 1, (2 - (float) (l*Math.cos(j*Math.PI/vert))) / 3, (2 - (float) (l*Math.cos(j*Math.PI/vert))) / 3, (2 - (float) (l*Math.cos(j*Math.PI/vert))) / 3, 1};
				//float[] tmpColor = new float[color.length + newColor.length];
				//System.arraycopy(color, 0, tmpColor, 0, color.length);
				//System.arraycopy(newColor, 0, tmpColor, color.length, newColor.length);
				
				//color = tmpColor;
				//vertex = tmp;

			}
		}
		
		for (int i = 0; i < vertexList.size(); i++){
			vertex[i] = vertexList.get(i);
		}
		
		for (int i = 0; i < colorList.size(); i++){
			color[i] = colorList.get(i);
		}

        ByteBuffer vBuff = ByteBuffer.allocateDirect(vertex.length*4);
        vBuff.order(ByteOrder.nativeOrder());
        FloatBuffer vertexBuff = vBuff.asFloatBuffer();
        vertexBuff.put(vertex);
        vertexBuff.position(0);
        
        ByteBuffer cBuff = ByteBuffer.allocateDirect(color.length*4);
        cBuff.order(ByteOrder.nativeOrder());
        FloatBuffer colorBuff = cBuff.asFloatBuffer();
        colorBuff.put(color);
        colorBuff.position(0);
        
        gl.glDisable(GL10.GL_CULL_FACE);
        gl.glFrontFace(GL10.GL_CW);
        gl.glShadeModel(GL10.GL_SMOOTH);
        gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vertexBuff);
        gl.glColorPointer(4, GL10.GL_FLOAT, 0, colorBuff);
        
        gl.glPushMatrix();
        gl.glTranslatef(translation[0], translation[1], translation[2]);
        
        gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, vertex.length / 3);	
        
        gl.glPopMatrix();
	}

	public void drawPlane(GL10 gl, float[] translation, float[] axis, float angle){
		
		float vertex[] = {-4, -4, 0,	4, -4, 0,	-4, 4, 0,	4, 4, 0,};
		float color[] = {0.2f, 1, 1, 1,		0.2f, 1, 1, 1,		0.2f, 1, 1, 1,		0.2f, 1, 1, 1};
	
		ByteBuffer vBuff = ByteBuffer.allocateDirect(vertex.length*4);
        vBuff.order(ByteOrder.nativeOrder());
        FloatBuffer vertexBuff = vBuff.asFloatBuffer();
        vertexBuff.put(vertex);
        vertexBuff.position(0);
        
        ByteBuffer cBuff = ByteBuffer.allocateDirect(color.length*4);
        cBuff.order(ByteOrder.nativeOrder());
        FloatBuffer colorBuff = cBuff.asFloatBuffer();
        colorBuff.put(color);
        colorBuff.position(0);

        gl.glDisable(GL10.GL_CULL_FACE);
        gl.glFrontFace(GL10.GL_CW);
        gl.glShadeModel(GL10.GL_SMOOTH);
        gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vertexBuff);
        gl.glColorPointer(4, GL10.GL_FLOAT, 0, colorBuff);
        gl.glPushMatrix();
        gl.glTranslatef(translation[0], translation[1], translation[2]);
        gl.glRotatef(angle, axis[0], axis[1], axis[2]);
        gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, vertex.length / 3);	
        gl.glPopMatrix();
	}
	
	/*
	public void drawBackground(GL10 gl, float[] translation, float[] axis, float angle){
		
		int[] textures = new int[1];
		FloatBuffer vertexBuffer;
		float vertices[] = {-4, -4, 0,	4, -4, 0,	-4, 4, 0,	4, 4, 0,};
		FloatBuffer textureBuffer;
		float texture[] = {
				0.0f, 1.0f, // top left
	            0.0f, 0.0f, // bottom left (V1)
	            1.0f, 1.0f, // top right (V4)
	            1.0f, 0.0f };
	
		ByteBuffer byteBuffer = ByteBuffer.allocateDirect(vertices.length*4);
		byteBuffer.order(ByteOrder.nativeOrder());
        vertexBuffer = byteBuffer.asFloatBuffer();
        vertexBuffer.put(vertices);
        vertexBuffer.position(0);
        
        byteBuffer = ByteBuffer.allocateDirect(texture.length * 4);
        byteBuffer.order(ByteOrder.nativeOrder());
        textureBuffer = byteBuffer.asFloatBuffer();
        textureBuffer.put(texture);
        textureBuffer.position(0);
        
        Bitmap bitmap;
        byteBuffer = ByteBuffer.allocate(image.width() * image.height() * 3);
        gl.glGenTextures(1, textures, 0);
        gl.glBindTexture(GL10.GL_TEXTURE_2D, textures[0]);
        
        gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
        gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);

        // Set the face rotation
        gl.glFrontFace(GL10.GL_CW);

        // Point to our vertex buffer
        gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vertexBuffer);
        gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, textureBuffer);

        // Draw the vertices as triangle strip
        gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, vertices.length / 3);

        // Disable the client state before leaving
        gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
        gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
	}
	*/
	
	public void touchBalls(float[] centerPt){
		if (((centerPt[0] > -4)&&(centerPt[0] < 4))&&((centerPt[1] > -4)&&(centerPt[1] < 4))){
			int i = (int) ((centerPt[0] + 4) / 2);
			int j = (int) ((centerPt[1] + 4) / 2);
			if (balls[i][j].state != -1) balls[i][j].state = -1;
		}
		NativeInit();
	}
		
	class Ball
	{
		float[] position;
		float angle;
		int state;
		
		Ball(float[] initialPos){
			
			position = initialPos;
			angle = 0;
			state = 0;
			
		}
		
		public float[] returnPos(){
			
			if (state == -1){
				if (angle == 90) state = 1;
				else angle = angle + 10;
			}
			if (state == 1){
				
				if (angle == 0) state = 0;
				else angle = angle - 10;
			}
			return new float[]{position[0], position[1], (float) (position[2] - Math.sin(Math.toRadians(angle)))};
		}
	}
	
	public void renderMat()
	{
		NativeSetUp();
		camImageLock.lock();
		try
		{
			NativeRenderMat(image.nativeObj, 0f, 0f, 0f);
		}
		finally
		{
			camImageLock.unlock();
		}
		NativeCleanUp();
	}
	
	public void finalize()
	{
		NativeCleanUp();
	}
	
	public native boolean NativeInit();
	public native boolean NativeRelease();
	
	public native boolean NativeSetUp();
	public native boolean NativeCleanUp();
	
	public native boolean NativeRenderMat(long addrMat, float x, float y, float z);
	public native boolean NativeUpdateLocation(float x, float y, float z);
}
